﻿using huqiang.FileTool;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace huqiang.Communication
{
    public class KcpFileCmd
    {
        /// <summary>
        /// 获取文件信息
        /// </summary>
        public const byte GetFileInfo = 0;
        /// <summary>
        /// 
        /// </summary>
        public const byte ResponesFileInfo = 1;
        /// <summary>
        /// 下载文件
        /// </summary>
        public const byte Download = 16;
        /// <summary>
        /// 下载文件
        /// </summary>
        public const byte ResponesDownload = 17;
        /// <summary>
        /// 下载准备完毕
        /// </summary>
        public const byte ReadyDownload = 18;
        /// <summary>
        /// 上传文件
        /// </summary>
        public const byte Upload = 32;
        /// <summary>
        /// 请求分卷，如果某个分卷丢失，则可以重新请求
        /// </summary>
        public const byte RequestPart = 48;
        /// <summary>
        /// 发送分卷
        /// </summary>
        public const byte SendPart = 49;
        /// <summary>
        /// 已接收分卷
        /// </summary>
        public const byte RecvPart = 50;
        /// <summary>
        /// 文件发送完毕
        /// </summary>
        public const byte SendCompelete = 64;
        /// <summary>
        /// 文件接收完毕
        /// </summary>
        public const byte RecvCompelete = 65;
    }
    public class KcpFileErrorCode
    {
        public const UInt16 NotFoundFile = 404;
    }
    public class KcpFileInfo
    {
        public long lastchangetime;
        public string url;
        public long length;
        public string msg;
    }
    public class KcpFileTransfer//:KcpLink
    {
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
        public struct FileHead
        {
            /// <summary>
            /// 数据类型
            /// </summary>
            public byte DataType;
            /// <summary>
            /// 数据类型
            /// </summary>
            public byte Cmd;
            /// <summary>
            /// 错误码
            /// </summary>
            public UInt16 Error;
            /// <summary>
            /// 分卷最大长度
            /// </summary>
            public UInt16 PartLen;
            /// <summary>
            /// 当前分卷
            /// </summary>
            public UInt16 CurPart;
            /// <summary>
            /// 附加文本信息长度
            /// </summary>
            public UInt16 Msglen;
            /// <summary>
            /// 文件id
            /// </summary>
            public long FileID;
            public unsafe static int Size = sizeof(FileHead);
        }
        public static int PartLength = DataPacker.FragmentDataSize * 32 - FileHead.Size;
        byte[] buffer = new byte[DataPacker.FragmentDataSize * 32];
        public List<KcpFileMission> downloads = new List<KcpFileMission>();
        public List<KcpFileMission> uploads = new List<KcpFileMission>();
        KcpLink kcpLink;
        public KcpFileTransfer(KcpLink link)
        {
            kcpLink = link;
        }
        internal void Dispatch(byte[] dat, int startIndex, int len)
        {
            if (dat == null)
                return;
            FileHead msg;
            string str = null;
            unsafe
            {
                fixed (byte* bp = &dat[startIndex])
                {
                    msg = *(FileHead*)bp;
                    int os = startIndex + FileHead.Size;
                    if (msg.Msglen > 0)
                    {
                        str = Encoding.UTF8.GetString(dat, os, msg.Msglen);
                    }
                    os += msg.Msglen;
                    len = len - FileHead.Size - msg.Msglen;
                    HandleMsg(ref msg, str, dat, os, len);
                }
            }
        }
        void HandleMsg(ref FileHead msg, string str, byte[] dat, int offset, int len)
        {
            if (msg.Error > 0)
            {

            }
            else
            {
                switch (msg.Cmd)
                {
                    case KcpFileCmd.SendPart:
                        HandleSendPart(ref msg, dat, offset, len);
                        break;
                    case KcpFileCmd.RecvPart:
                        HandleRecvPart(ref msg);
                        break;
                    case KcpFileCmd.RequestPart:
                        HandleRequestPart(ref msg);
                        break;
                    case KcpFileCmd.GetFileInfo:
                        HandleGetFileInfo(str);
                        break;
                    case KcpFileCmd.ResponesFileInfo:
                        HandleResponesFileInfo(str);
                        break;
                    case KcpFileCmd.Download:
                        HandleDownload(str);
                        break;
                    case KcpFileCmd.ResponesDownload:
                        HandleResponseDownload(ref msg, str);
                        break;
                    case KcpFileCmd.ReadyDownload:
                        HandleReadyDownload(ref msg, dat, offset, len);
                        break;
                    case KcpFileCmd.Upload:
                        HandleUpload(ref msg, str);
                        break;
                    case KcpFileCmd.SendCompelete:

                        break;
                    case KcpFileCmd.RecvCompelete:
                        HandleRecvCompelete(ref msg);
                        break;
                }
            }
        }
        internal void ContinuousSend()
        {
            if (kcpLink.Connected)
            {
                long tick = DateTime.Now.Ticks;
                UpdateDownload(tick);
                UpdateUpload(tick);
            }
        }
        void UpdateDownload(long time)
        {
            int c = downloads.Count - 1;
            for (int i = c; i >= 0; i--)
            {
                var miss = downloads[i];
                if (time - miss.LastRecvTime > 100 * 1000 * 10000)//任务超时100秒
                {
                    miss.Dispose();
                    downloads.RemoveAt(i);
                    if (kcpLink.kcp.OnUploadError != null)
                        kcpLink.kcp.OnUploadError(kcpLink, miss);
                }
                else
                {
                    switch (miss.State)
                    {
                        case KcpFileMission.MissionState.Done:
                            miss.Dispose();
                            downloads.RemoveAt(i);
                            break;
                        case KcpFileMission.MissionState.None:
                            Download(miss);
                            break;
                            //case KcpFileMission.MissionState.RequestDownload:
                            //    break;
                            //case KcpFileMission.MissionState.Transmission:
                            //    break;
                    }
                }
            }
        }
        void UpdateUpload(long time)
        {
            int c = uploads.Count - 1;
            for (int i = c; i >= 0; i--)
            {
                var miss = uploads[i];
                if (time - miss.LastRecvTime > 100 * 1000 * 10000)//任务超时100秒
                {
                    miss.Dispose();
                    uploads.RemoveAt(i);
                    if (kcpLink.kcp.OnUploadError != null)
                        kcpLink.kcp.OnUploadError(kcpLink, miss);
                }
                else
                {
                    switch (miss.State)
                    {
                        case KcpFileMission.MissionState.None:
                            UploadRequest(miss);
                            miss.State = KcpFileMission.MissionState.RequestInfo;
                            break;
                        case KcpFileMission.MissionState.Transmission:
                            if (kcpLink.dataPacker.Remain> 64)
                            {
                                SendPart(miss);
                            }
                            break;
                        case KcpFileMission.MissionState.Done:
                            miss.Dispose();
                            uploads.RemoveAt(i);
                            break;
                    }
                }
            }
        }
        void SendMsg(ref FileHead head)
        {
            head.DataType = EnvelopeType.File;
            head.Msglen = 0;
            byte[] buf = new byte[FileHead.Size];
            unsafe
            {
                fixed (byte* bp = &buf[0])
                {
                    *(FileHead*)bp = head;
                }
            }
            kcpLink.Post(buf);
        }
        void SendMsg(ref FileHead head, string msg)
        {
            byte[] dat = Encoding.UTF8.GetBytes(msg);
            head.Msglen = (UInt16)dat.Length;
            SendData(ref head, dat);
        }
        void SendData(ref FileHead head, byte[] dat)
        {
            head.DataType = EnvelopeType.File;
            byte[] buf = new byte[FileHead.Size + dat.Length];
            unsafe
            {
                fixed (byte* bp = &buf[0])
                {
                    *(FileHead*)bp = head;
                }
            }
            int s = FileHead.Size;
            for (int i = 0; i < dat.Length; i++)
            {
                buf[s] = dat[i];
                s++;
            }
            kcpLink.Post(buf);
        }
        /// <summary>
        /// 请求对方发送分卷
        /// </summary>
        void HandleRequestPart(ref FileHead msg)
        {
            for (int i = 0; i < uploads.Count; i++)
            {
                if (uploads[i].FileID == msg.FileID)
                {
                    var mission = uploads[i];
                    int len = PartLength;
                    int part = msg.CurPart;//当前请求的分卷
                    if (part * len > mission.Length)
                        return;
                    long pos = msg.CurPart * len;
                    if (len + pos > mission.Length)
                        len = (int)(mission.Length - pos);
                    int dc = len + FileHead.Size;
                    byte[] buf = new byte[dc];

                    //KcpStreamManager.ReadData(mission.FileID, pos, buf, FileHead.Size, len);
                    mission.ReadData(pos, buf, FileHead.Size, len);
                    unsafe
                    {
                        fixed (byte* bp = &buf[0])
                        {
                            FileHead* head = (FileHead*)bp;
                            head->DataType = EnvelopeType.File;
                            head->Cmd = KcpFileCmd.SendPart;
                            head->PartLen = (UInt16)len;
                            head->CurPart = (UInt16)part;
                            head->FileID = mission.FileID;
                        }
                    }
                    kcpLink.dataPacker.AddSendMsg(buf, KcpServer.SendTime);
                    break;
                }
            }
        }
        /// <summary>
        /// 发送文件分卷
        /// </summary>
        bool SendPart(KcpFileMission mission)
        {
            int len = PartLength;
            int part = mission.CurrentPart;
            if (part * len > mission.Length)
                return false;
            long pos = mission.CurrentPart * len;
            if (len + pos > mission.Length)
                len = (int)(mission.Length - pos);
            //byte[] buf = new byte[len + FileHead.Size];

            //KcpStreamManager.ReadData(mission.FileID, pos, buf, FileHead.Size, len);
            mission.ReadData(pos, buffer, FileHead.Size, len);
            unsafe
            {
                fixed (byte* bp = &buffer[0])
                {
                    FileHead* head = (FileHead*)bp;
                    head->DataType = EnvelopeType.File;
                    head->Cmd = KcpFileCmd.SendPart;
                    head->PartLen = (UInt16)len;
                    head->CurPart = (UInt16)part;
                    head->FileID = mission.FileID;
                }
            }
            kcpLink.dataPacker.AddSendMsg(buffer, KcpServer.SendTime);
            mission.MoveNextPart();
            return true;
        }
        /// <summary>
        /// 处理来自对方的分卷
        /// </summary>
        /// <param name="head"></param>
        /// <param name="dat"></param>
        /// <param name="offset"></param>
        /// <param name="len"></param>
        void HandleSendPart(ref FileHead head, byte[] dat, int offset, int len)
        {
            for (int i = 0; i < downloads.Count; i++)
            {
                var kfm = downloads[i];
                if (kfm.FileID == head.FileID)
                {
                    kfm.LastRecvTime = DateTime.Now.Ticks;
                    int pl = PartLength;
                    long pos = head.CurPart * pl;
                    kfm.WriteData(pos, dat, offset, len);
                    kfm.WriteRecord(head.CurPart);//添加分卷下载记录
                    head.Cmd = KcpFileCmd.RecvPart;
                    SendMsg(ref head);
                    if (kfm.Compelete)//下载完毕
                    {
                        head.Cmd = KcpFileCmd.RecvCompelete;
                        SendMsg(ref head);
                        downloads.RemoveAt(i);
                        kfm.Dispose();
                        if (kcpLink.kcp.OnDownloadCompelete != null)
                            kcpLink.kcp.OnDownloadCompelete(kcpLink, kfm);
                    }
                    break;
                }
            }
        }
        /// <summary>
        /// 标记对方已成功接收此分卷
        /// </summary>
        /// <param name="head"></param>
        void HandleRecvPart(ref FileHead head)
        {
            for (int i = 0; i < uploads.Count; i++)
            {
                var miss = uploads[i];
                if (miss.FileID == head.FileID)
                {
                    miss.WriteRecord(head.CurPart);
                    miss.LastRecvTime = DateTime.Now.Ticks;
                    miss.TransferCount += head.PartLen;
                    break;
                }
            }
        }
        /// <summary>
        /// 获取文件信息
        /// </summary>
        public void GetFileInfo(string url)
        {
            FileHead file = new FileHead();
            file.Cmd = KcpFileCmd.GetFileInfo;
            SendMsg(ref file, "url=" + url);
        }
        void HandleGetFileInfo(string str)
        {
            if (str != null)
            {
                KcpFileInfo fileInfo = ReadFileInfo(str);
                var fi = new FileInfo(fileInfo.url);
                if (fi.Exists)
                {
                    fileInfo.lastchangetime = fi.LastWriteTime.Ticks;
                    fileInfo.length = fi.Length;
                }
                StringBuilder sb = new StringBuilder();
                sb.Append("url=");
                sb.Append(fileInfo.url);
                sb.Append("\nlength=");
                sb.Append(fileInfo.length);
                sb.Append("\nlastchangetime=");
                sb.Append(fileInfo.lastchangetime);
                FileHead head = new FileHead();
                head.Cmd = KcpFileCmd.ResponesFileInfo;
                SendMsg(ref head, sb.ToString());
            }
        }
        void HandleResponesFileInfo(string str)
        {
            if (str != null)
            {
                KcpFileInfo fileInfo = ReadFileInfo(str);

            }
        }
        /// <summary>
        /// 创建下载任务
        /// </summary>
        void Download(KcpFileMission mission)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("url=");
            sb.Append(mission.url);
            if (mission.msg != null)
            {
                sb.Append("\nmsg=");
                sb.Append(mission.msg);
            }
            FileHead head = new FileHead();
            head.Cmd = KcpFileCmd.Download;
            SendMsg(ref head, sb.ToString());
            mission.State = KcpFileMission.MissionState.RequestDownload;
        }
        void HandleDownload(string str)
        {
            FileHead file = new FileHead();
            KcpFileInfo fileInfo = ReadFileInfo(str);
            string realpath = fileInfo.url;
            if (kcpLink.kcp.OnBeforeDownload != null)
                realpath = kcpLink.kcp.OnBeforeDownload(kcpLink, fileInfo);
            KcpFileMission mission = new KcpFileMission();
            mission.LastRecvTime = DateTime.Now.Ticks;
            mission.method = KcpFileMission.MissionMethod.HandleDownload;
            mission.State = KcpFileMission.MissionState.RequestInfo;
            mission.shareFile = ShareFile.Open(realpath);
            mission.FileID =
            file.FileID = mission.shareFile.FileID;//KcpStreamManager.OpenDownload(fileInfo, realpath);
            var info = new FileInfo(realpath);
            mission.Length = info.Length;
            mission.LastChangeTime = info.LastWriteTime.Ticks;
            file.Cmd = KcpFileCmd.ResponesDownload;

            StringBuilder sb = new StringBuilder();
            sb.Append("url=");
            sb.Append(fileInfo.url);
            sb.Append("\nlength=");
            sb.Append(mission.Length);
            sb.Append("\nlastchangetime=");
            sb.Append(mission.LastChangeTime);
            SendMsg(ref file, sb.ToString());
            uploads.Add(mission);
        }
        void HandleResponseDownload(ref FileHead head, string str)
        {
            if (str != null)
            {
                KcpFileInfo info = ReadFileInfo(str);
                for (int i = 0; i < downloads.Count; i++)
                {
                    var miss = downloads[i];
                    if (downloads[i].url == info.url)
                    {
                        if (miss != null)
                        {
                            if (head.FileID == 0)//远程连接未能找到目标文件
                            {
                                downloads.RemoveAt(i);
                                if (kcpLink.kcp.OnDownloadError != null)
                                    kcpLink.kcp.OnDownloadError(kcpLink, miss);
                            }
                            else
                            {
                                miss.FileID = head.FileID;
                                miss.Length = info.length;
                                miss.LastChangeTime = info.lastchangetime;
                                FileHead file = new FileHead();
                                file.Cmd = KcpFileCmd.ReadyDownload;
                                file.FileID = head.FileID;
                                miss.ReadyDownload();
                                SendData(ref file, miss.GetRecord());//发送下载记录
                            }
                        }
                        break;
                    }
                }
            }
        }
        void HandleReadyDownload(ref FileHead head, byte[] dat, int offset, int len)
        {
            for (int i = 0; i < uploads.Count; i++)
            {
                var miss = uploads[i];
                if (head.FileID == uploads[i].FileID)
                {
                    miss.State = KcpFileMission.MissionState.Transmission;
                    byte[] tmp = new byte[len];
                    for (int j = 0; j < len; j++)
                    {
                        tmp[j] = dat[offset];
                        offset++;
                    }
                    miss.SetRecord(tmp);
                    return;
                }
            }
        }
        void UploadRequest(KcpFileMission mission)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("url=");
            sb.Append(mission.url);
            sb.Append("\nlastchangetime=");
            sb.Append(mission.LastChangeTime);
            sb.Append("\nlength=");
            sb.Append(mission.Length);
            sb.Append("\nmsg=");
            if (mission.msg != null)
                sb.Append(mission.msg);
            FileHead head = new FileHead();
            head.Cmd = KcpFileCmd.Upload;
            head.FileID = mission.FileID;
            SendMsg(ref head, sb.ToString());
        }
        void HandleUpload(ref FileHead head, string str)
        {
            KcpFileInfo fileInfo = ReadFileInfo(str);
            string realpath = fileInfo.url;
            if (kcpLink.kcp.OnBeforeUpload != null)
                realpath = kcpLink.kcp.OnBeforeUpload(kcpLink, fileInfo);
            KcpFileMission kfm = new KcpFileMission();
            kfm.realPath = realpath;
            kfm.url = fileInfo.url;
            kfm.msg = fileInfo.msg;
            kfm.Length = fileInfo.length;
            kfm.FileID = head.FileID;
            kfm.State = KcpFileMission.MissionState.Transmission;
            kfm.method = KcpFileMission.MissionMethod.HandleUpload;
            kfm.ReadyDownload();
            kfm.LastRecvTime = DateTime.Now.Ticks;
            downloads.Add(kfm);
            head.Cmd = KcpFileCmd.ReadyDownload;
            head.Msglen = 0;
            SendData(ref head, kfm.GetRecord());//发送下载记录
        }
        void HandleRecvCompelete(ref FileHead head)
        {
            for (int i = 0; i < uploads.Count; i++)
            {
                var kfm = uploads[i];
                if (kfm.FileID == head.FileID)
                {
                    kfm.Dispose();
                    uploads.RemoveAt(i);
                    if (kcpLink.kcp.OnUploadCompelete != null)
                        kcpLink.kcp.OnUploadCompelete(kcpLink, kfm);
                    break;
                }
            }
        }
        public void Download(string url, string savePath, string msg)
        {
            for (int i = 0; i < downloads.Count; i++)
                if (downloads[i].url == url)
                    return;
            KcpFileMission kfm = new KcpFileMission();
            kfm.realPath = savePath;
            kfm.url = url;
            kfm.msg = msg;
            kfm.LastRecvTime = DateTime.Now.Ticks;
            kfm.method = KcpFileMission.MissionMethod.Dowanload;
            downloads.Add(kfm);
        }
        public void Upload(string url, string filePath, string msg)
        {
            for (int i = 0; i < uploads.Count; i++)
                if (uploads[i].url == url)
                    return;
            KcpFileMission kfm = new KcpFileMission();
            kfm.realPath = filePath;
            kfm.url = url;
            kfm.msg = msg;
            kfm.OpenUpload();
            kfm.LastRecvTime = DateTime.Now.Ticks;
            kfm.method = KcpFileMission.MissionMethod.Upload;
            uploads.Add(kfm);
        }
        KcpFileInfo ReadFileInfo(string str)
        {
            KcpFileInfo info = new KcpFileInfo();
            info.url = GetString(str, "url");
            info.lastchangetime = GetLong(str, "lastchangetime");
            info.length = GetLong(str, "length");
            info.msg = GetString(str, "msg");
            return info;
        }
        string GetString(string str, string key)
        {
            int s = str.IndexOf(key);
            if (s < 0)
                return null;
            s += key.Length + 1;
            int e = str.IndexOf('\n', s);
            if (e < 0)
                e = str.Length;
            return str.Substring(s, e - s);
        }
        int GetInt(string str, string key)
        {
            int s = str.IndexOf(key);
            if (s < 0)
                return 0;
            s += key.Length + 1;
            int e = str.IndexOf('\n', s);
            if (e < 0)
                e = str.Length;
            string p = str.Substring(s, e - s);
            int l = 0;
            int.TryParse(p, out l);
            return l;
        }
        long GetLong(string str, string key)
        {
            int s = str.IndexOf(key);
            if (s < 0)
                return 0;
            s += key.Length + 1;
            int e = str.IndexOf('\n', s);
            if (e < 0)
                e = str.Length;
            string p = str.Substring(s, e - s);
            long l = 0;
            long.TryParse(p, out l);
            return l;
        }
    }
}
